package com.hucheng.common.utils;

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.core.RedisConnectionUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.types.Expiration;

import java.nio.charset.Charset;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @Author: 凉白开不加冰
 * @Version: 0.0.1V
 * @Date: 2018/8/7
 * @Description: redis工具类
 **/
@Slf4j
public class RedisUtils {

    /**
     * @param stringRedisTemplate
     * @param key
     * @param time
     * @Author: 凉白开不加冰
     * @Date: 2018/8/7 15:12
     * @Description: 设置过期时间
     **/
    public static boolean expire(RedisTemplate stringRedisTemplate, String key, long time) {
        try {
            if (time > 0) {
                stringRedisTemplate.expire(key, time, TimeUnit.SECONDS);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * @param stringRedisTemplate
     * @param key
     * @Author: 凉白开不加冰
     * @Date: 2018/8/7 15:14
     * @Description: 根据key获取过期时间
     **/
    public static long getExpire(RedisTemplate stringRedisTemplate, String key) {
        return stringRedisTemplate.getExpire(key, TimeUnit.SECONDS);
    }

    /**
     * @param stringRedisTemplate
     * @param key
     * @param value
     * @Author: 凉白开不加冰
     * @Date: 2018/8/7 14:05
     * @Description: 普通数据存入redis
     **/
    public static boolean set(RedisTemplate stringRedisTemplate, String key, String value) {
        try {
            stringRedisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * @param stringRedisTemplate
     * @param key
     * @param value
     * @param time
     * @Author: 凉白开不加冰
     * @Date: 2018/8/7 14:13
     * @Description: 普通存入数据redis, 并且设置过期时间
     **/
    public static boolean set(RedisTemplate stringRedisTemplate, String key, String value, long time) {
        try {
            if (time > 0) {
                stringRedisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            } else {
                set(stringRedisTemplate, key, value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * @param stringRedisTemplate
     * @param key
     * @Author: 凉白开不加冰
     * @Date: 2018/8/7 14:16
     * @Description: 普通获取数据redis
     **/
    public static Object get(RedisTemplate stringRedisTemplate, String key) {
        return key == null ? null : stringRedisTemplate.opsForValue().get(key);
    }

    /**
     * @param stringRedisTemplate
     * @param key
     * @Author: 凉白开不加冰
     * @Date: 2018/8/7 14:16
     * @Description: 普通获取数据redis
     **/
    public static String getString(RedisTemplate stringRedisTemplate, String key) {
        Object value = get(stringRedisTemplate, key);
        return value == null ? null : value.toString();
    }

    /**
     * @param stringRedisTemplate
     * @param key
     * @Author: 凉白开不加冰
     * @Date: 2018/8/7 14:16
     * @Description: 普通获取数据redis
     **/
    public static Integer getInteger(RedisTemplate stringRedisTemplate, String key) {
        String value = getString(stringRedisTemplate, key);
        return value == null ? 0 : Integer.parseInt(value);
    }

    /**
     * @param stringRedisTemplate
     * @param key
     * @Author: 凉白开不加冰
     * @Date: 2018/8/7 14:16
     * @Description: 普通获取数据redis
     **/
    public static <T> T get(RedisTemplate stringRedisTemplate, String key, Class<T> clazz) {
        String value = getString(stringRedisTemplate, key);
        return value == null ? null : JSON.parseObject(value, clazz);
    }

    public static boolean hasKey(RedisTemplate stringRedisTemplate, String key) {
        return stringRedisTemplate.hasKey(key);
    }

    /**
     * @Author: 凉白开不加冰
     * @Date: 2019-06-20 11:08
     * @Description: 删除缓存
     **/
    public static boolean del(RedisTemplate stringRedisTemplate, String key) {
        return stringRedisTemplate.delete(key);
    }

    /**
     * @param stringRedisTemplate
     * @param key
     * @param value
     * @Author: 凉白开不加冰
     * @Date: 2018/8/7 14:31
     * @Description: 递增
     **/
    public static long incr(RedisTemplate stringRedisTemplate, String key, long value) {
        if (value <= 0) {
            throw new RuntimeException("递增因子必须大于0");
        }
        return stringRedisTemplate.opsForValue().increment(key, value);
    }

    /**
     * @param stringRedisTemplate
     * @param key
     * @param value
     * @Author: 凉白开不加冰
     * @Date: 2018/8/7 14:38
     * @Description: 递增double
     **/
    public static double incrDouble(RedisTemplate stringRedisTemplate, String key, double value) {
        if (value <= 0) {
            throw new RuntimeException("递增因子必须大于0");
        }
        return stringRedisTemplate.opsForValue().increment(key, value);
    }

    /**
     * @param stringRedisTemplate
     * @param key
     * @param value
     * @Author: 凉白开不加冰
     * @Date: 2018/8/7 14:40
     * @Description: 递减
     **/
    public static long decr(RedisTemplate stringRedisTemplate, String key, long value) {
        if (value <= 0) {
            throw new RuntimeException("递增因子必须大于0");
        }
        return stringRedisTemplate.opsForValue().decrement(key, value);
    }

    /**
     * @param stringRedisTemplate
     * @param key
     * @param value
     * @Author: 凉白开不加冰
     * @Date: 2018/8/7 14:42
     * @Description: 递减double
     **/
    public static double decrDouble(RedisTemplate stringRedisTemplate, String key, double value) {
        if (value <= 0) {
            throw new RuntimeException("递增因子必须大于0");
        }
        return stringRedisTemplate.opsForValue().increment(key, -value);
    }

    /**
     * @param stringRedisTemplate
     * @param key                 redis key
     * @param item                key
     * @Author: 凉白开不加冰
     * @Date: 2018/8/7 14:54
     * @Description: 从hash表中获取数据
     **/
    public static Object hget(RedisTemplate stringRedisTemplate, String key, String item) {
        return stringRedisTemplate.opsForHash().get(key, item);
    }

    public static List<Object> hgetList(RedisTemplate stringRedisTemplate, String key) {
        return stringRedisTemplate.opsForHash().values(key);
    }

    public static Map<Object, Object> hgetMap(RedisTemplate stringRedisTemplate, String key) {
        return stringRedisTemplate.opsForHash().entries(key);
    }

    public static <T> Map<Object, Object> getMap(RedisTemplate stringRedisTemplate, String key) {
        Map<Object, Object> map = stringRedisTemplate.boundHashOps(key).entries();
        return map;
    }

    /**
     * @param stringRedisTemplate
     * @param key                 redis key
     * @param item                key
     * @param value               value
     * @Author: 凉白开不加冰
     * @Date: 2018/8/7 14:48
     * @Description: 向一张hash表中放入数据, 如果不存在将创建
     **/
    public static boolean hset(RedisTemplate stringRedisTemplate, String key, String item, Object value) {
        try {
            stringRedisTemplate.opsForHash().put(key, item, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * @param stringRedisTemplate
     * @param key                 redis key
     * @param item                key
     * @param value               value
     * @Author: 凉白开不加冰
     * @Date: 2018/8/7 14:48
     * @Description: 向一张hash表中放入数据, 如果不存在将创建并设置过期时间
     **/
    public static boolean hset(RedisTemplate stringRedisTemplate, String key, String item, Object value, long time) {
        try {
            hset(stringRedisTemplate, key, item, value);
            if (time > 0) {
                expire(stringRedisTemplate, key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * @param stringRedisTemplate
     * @param key
     * @param map
     * @Author: 凉白开不加冰
     * @Date: 2018/8/7 15:01
     * @Description: 向hash表中放入数据, 对应多个键值
     **/
    public static boolean hmset(RedisTemplate stringRedisTemplate, String key, Map<String, Object> map) {
        try {
            stringRedisTemplate.opsForHash().putAll(key, map);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * @param stringRedisTemplate
     * @param key
     * @param map
     * @Author: 凉白开不加冰
     * @Date: 2018/8/7 15:01
     * @Description: 向hash表中放入数据, 对应多个键值并设置超时间
     **/
    public static boolean hmset(RedisTemplate stringRedisTemplate, String key, Map<String, Object> map, long time) {
        try {
            stringRedisTemplate.opsForHash().putAll(key, map);
            if (time > 0) {
                expire(stringRedisTemplate, key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * @param stringRedisTemplate
     * @param key
     * @param item
     * @Author: 凉白开不加冰
     * @Date: 2018/8/7 15:21
     * @Description: 删除hash表中的数据
     **/
    public static void hdel(RedisTemplate stringRedisTemplate, String key, Object... item) {
        stringRedisTemplate.opsForHash().delete(key, item);
    }

    /**
     * @param stringRedisTemplate
     * @param key
     * @param item
     * @Author: 凉白开不加冰
     * @Date: 2018/8/7 15:26
     * @Description: 判断hash中是否存在该值
     **/
    public static boolean hHashkey(RedisTemplate stringRedisTemplate, String key, String item) {
        return stringRedisTemplate.opsForHash().hasKey(key, item);
    }

    /**
     * @param stringRedisTemplate
     * @param key
     * @param item
     * @param by
     * @Author: 凉白开不加冰
     * @Date: 2018/8/7 15:29
     * @Description: hash递增
     **/
    public static double hincr(RedisTemplate stringRedisTemplate, String key, String item, double by) {
        return stringRedisTemplate.opsForHash().increment(key, item, by);
    }

    /**
     * @param stringRedisTemplate
     * @param key
     * @param item
     * @param by
     * @Author: 凉白开不加冰
     * @Date: 2018/8/7 15:33
     * @Description: hash递减
     **/
    public static double hdecr(RedisTemplate stringRedisTemplate, String key, String item, double by) {
        return stringRedisTemplate.opsForHash().increment(key, item, -by);
    }

    /**
     * @param stringRedisTemplate
     * @param key
     * @Author: 凉白开不加冰
     * @Date: 2018/8/7 15:37
     * @Description: 根据key获取Set中的所有值
     **/
    public static Set<String> sGet(RedisTemplate stringRedisTemplate, String key) {
        try {
            return stringRedisTemplate.opsForSet().members(key);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * @param stringRedisTemplate
     * @param key
     * @param value
     * @Author: 凉白开不加冰
     * @Date: 2018/8/7 15:40
     * @Description: 根据value从一个set中查询, 是否存在
     **/
    public static boolean sHashkey(RedisTemplate stringRedisTemplate, String key, Object value) {
        try {
            return stringRedisTemplate.opsForSet().isMember(key, value);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * @param stringRedisTemplate
     * @param key
     * @param obj
     * @Author: 凉白开不加冰
     * @Date: 2018/8/7 15:44
     * @Description: 将数据放入set缓存
     **/
    public static long sSet(RedisTemplate stringRedisTemplate, String key, Object... obj) {
        try {
            return stringRedisTemplate.opsForSet().add(key, (String[]) obj);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * @param stringRedisTemplate
     * @param key
     * @param time
     * @param value
     * @Author: 凉白开不加冰
     * @Date: 2018/8/7 16:12
     * @Description: 将set放入缓存, 并设置时间
     **/
    public static long sSetAndTime(RedisTemplate stringRedisTemplate, String key, long time, Object... value) {
        try {
            long count = stringRedisTemplate.opsForSet().add(key, (String[]) value);
            if (time > 0) {
                expire(stringRedisTemplate, key, time);
            }
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * @param stringRedisTemplate
     * @param key
     * @param value
     * @Author: 凉白开不加冰
     * @Date: 2018/8/7 16:51
     * @Description: 将数据放入list左边
     **/
    public static Long lpush(RedisTemplate stringRedisTemplate, String key, String value) {
        return stringRedisTemplate.opsForList().leftPush(key, value);
    }

    /**
     * @param stringRedisTemplate
     * @param key
     * @param value
     * @param time
     * @Author: 凉白开不加冰
     * @Date: 2018/8/7 17:08
     * @Description: 将数据放入list左边并设置过期时间
     **/
    public static long lpush(RedisTemplate stringRedisTemplate, String key, String value, long time) {
        long l = lpush(stringRedisTemplate, key, value);
        if (time > 0) {
            expire(stringRedisTemplate, key, time);
        }
        return l;
    }

    /**
     * @param stringRedisTemplate
     * @param key
     * @Author: 凉白开不加冰
     * @Date: 2018/8/7 16:58
     * @Description: 从左边list取出数据
     **/
    public static String lpop(RedisTemplate stringRedisTemplate, String key) {
        return String.valueOf(stringRedisTemplate.opsForList().leftPop(key));
    }

    /**
     * @param stringRedisTemplate
     * @param key
     * @param value
     * @Author: 凉白开不加冰
     * @Date: 2018/8/7 17:03
     * @Description: 将数据放入list右边
     **/
    public static Long rpush(RedisTemplate stringRedisTemplate, String key, String value) {
        return stringRedisTemplate.opsForList().rightPush(key, value);
    }

    /**
     * @param stringRedisTemplate
     * @param key
     * @param value
     * @param time
     * @Author: 凉白开不加冰
     * @Date: 2018/8/7 17:03
     * @Description: 将数据放入list右边
     **/
    public static long rpush(RedisTemplate stringRedisTemplate, String key, String value, long time) {
        long l = rpush(stringRedisTemplate, key, value);
        if (time > 0) {
            expire(stringRedisTemplate, key, time);
        }
        return l;
    }

    /**
     * @param stringRedisTemplate
     * @param key
     * @Author: 凉白开不加冰
     * @Date: 2018/8/7 17:04
     * @Description: 从list右边取出数据
     **/
    public static String rpop(RedisTemplate stringRedisTemplate, String key) {
        return String.valueOf(stringRedisTemplate.opsForList().rightPop(key));
    }

    /**
     * @param stringRedisTemplate
     * @param key
     * @param value
     * @Author: 凉白开不加冰
     * @Date: 2018/8/7 17:51
     * @Description: 根据value删除
     **/
    public static void remove(RedisTemplate stringRedisTemplate, String key, String value) {
        stringRedisTemplate.opsForList().remove(key, 0, value);
    }

//    /**
//     * 加锁
//     *
//     * @param key   seckillId
//     * @param value 当前时间+超时时间
//     * @return
//     */
//    public static boolean lock(RedisTemplate redisTemplate, String key, String value) {
//        // 可以设置返回true
//        Boolean isLock = redisTemplate.opsForValue().setIfAbsent(key, value);
//        if (isLock) {
//            return true;
//        }
//        String currentValue = redisTemplate.opsForValue().get(key).toString();
//        // 如果锁已经过期
//        if (!ValidatorUtils.isEmpty(currentValue)
//                && Long.valueOf(currentValue) < System.currentTimeMillis()) {
//            // 获取上一个锁的时间，并设置新锁的时间
//            String oldValue = redisTemplate.opsForValue().getAndSet(key, value).toString();
//            if (!ValidatorUtils.isEmpty(oldValue)
//                    && oldValue.equals(currentValue)) {
//                log.info("锁过期并返回true");
//                return true;
//            }
//        }
//        return false;
//    }
//
//    /**
//     * 解锁
//     *
//     * @param key
//     * @return
//     */
//    public static void unlock(RedisTemplate redisTemplate, String key, String value) {
//        try {
//            String currentValue = redisTemplate.opsForValue().get(key).toString();
//            if (!ValidatorUtils.isEmpty(currentValue)
//                    && currentValue.equals(value)) {
//                redisTemplate.opsForValue().getOperations().delete(key);
//            }
//        } catch (Exception e) {
//            log.error("redis分布式锁，解锁异常, {}", e.getMessage());
//        }
//
//    }

    /**
     * 加锁，有阻塞
     *
     * @param name
     * @param expire
     * @param timeout
     * @return
     */
    public static String lock(RedisTemplate stringRedisTemplate, String name, long expire, long timeout) {
        long startTime = System.currentTimeMillis();
        String token;
        do {
            token = tryLock(stringRedisTemplate, name, expire);
            if (token == null) {
                if ((System.currentTimeMillis() - startTime) > (timeout - 50)) {
                    break;
                }
                try {
                    //try 50 per sec
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    return null;
                }
            }
        } while (token == null);

        return token;
    }

    /**
     * 加锁，无阻塞
     *
     * @param name
     * @param expire
     * @return
     */
    public static String tryLock(RedisTemplate stringRedisTemplate, String name, long expire) {
        String token = UUID.randomUUID().toString();
        RedisConnectionFactory factory = stringRedisTemplate.getConnectionFactory();
        RedisConnection conn = factory.getConnection();
        try {
            Boolean result = conn.set(name.getBytes(Charset.forName("UTF-8")), token.getBytes(Charset.forName("UTF-8")),
                    Expiration.from(expire, TimeUnit.MILLISECONDS), RedisStringCommands.SetOption.SET_IF_ABSENT);
            if (result != null && result) {
                return token;
            }
        } finally {
            RedisConnectionUtils.releaseConnection(conn, factory);
        }
        return null;
    }

    /**
     * 解锁脚本，原子操作
     */
    private static final String unlockScript =
            "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n"
                    + "then\n"
                    + "    return redis.call(\"del\",KEYS[1])\n"
                    + "else\n"
                    + "    return 0\n"
                    + "end";

    /**
     * 解锁
     *
     * @param name
     * @param token
     * @return
     */
    public static boolean unlock(RedisTemplate stringRedisTemplate, String name, String token) {
        byte[][] keysAndArgs = new byte[2][];
        keysAndArgs[0] = name.getBytes(Charset.forName("UTF-8"));
        keysAndArgs[1] = token.getBytes(Charset.forName("UTF-8"));
        RedisConnectionFactory factory = stringRedisTemplate.getConnectionFactory();
        RedisConnection conn = factory.getConnection();
        try {
            Long result = (Long) conn.scriptingCommands().eval(unlockScript.getBytes(Charset.forName("UTF-8")), ReturnType.INTEGER, 1, keysAndArgs);
            if (result != null && result > 0)
                return true;
        } finally {
            RedisConnectionUtils.releaseConnection(conn, factory);
        }

        return false;
    }


}
